了解捕捉值和额外参数之间的优先级 额外的选项
当冲突出现的时候,额外URLconf参数优先于捕捉值。也就是说,如果URLconf捕捉到的一个命名组变量和一个额外URLconf参数包含的变量同名时,额外URLconf参数的值会被使用。
例如,下面这个URLconf:
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^mydata/(?P<id>\d+)/$', views.my_view, {'id': 3}),
)
这里,正则表达式和额外字典都包含了一个id
。硬编码的(额外字典的)id
将优先使用。就是说任何请求(比如,/mydata/2/
或者/mydata/432432/
)都会作id
设置为对待,不管URL里面能捕捉到什么样的值。
聪明的读者会发现在这种情况下,在正则表达式里面写上捕捉是浪费时间的,因为id
的值总是会被字典中的值覆盖。没错,我们说这个的目的只是为了让你不要犯这样的错误。
使用缺省视图参数
另外一个方便的特性是你可以给一个视图指定默认的参数。这样,当没有给这个参数赋值的时候将会使用默认的值。
例子:
# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^blog/$', views.page),
(r'^blog/page(?P<num>\d+)/$', views.page),
)
# views.py
def page(request, num='1'):
# Output the appropriate page of blog entries, according to num.
# ...
在这里,两个URL表达式都指向了同一个视图views.page
,但是第一个表达式没有传递任何参数。如果匹配到了第一个样式,page()
函数将会对参数num
使用默认值"1"
,如果第二个表达式匹配成功,page()
函数将使用正则表达式传递过来的num的值。
(注:我们已经注意到设置默认参数值是字符串‘1’
,不是整数1
。为了保持一致,因为捕捉给num
的值总是字符串。
就像前面解释的一样,这种技术与配置选项的联用是很普遍的。以下这个例子比提供视图配置选项一节中的例子有些许的改进。
def my_view(request, template_name='mysite/my_view.html'):
var = do_something()
return render_to_response(template_name, {'var': var})
特殊情况下的视图
有时你有一个模式来处理在你的URLconf中的一系列URL,但是有时候需要特别处理其中的某个URL。在这种情况下,要使用将URLconf中把特殊情况放在首位的线性处理方式 。
比方说,你可以考虑通过下面这个URLpattern所描述的方式来向Django的管理站点添加一个目标页面
urlpatterns = patterns('',
# ...
('^([^/]+)/([^/]+)/add/$', views.add_stage),
# ...
)
这将匹配像 /myblog/entries/add/
和 /auth/groups/add/
这样的URL 。然而,对于用户对象的添加页面( /auth/user/add/
)是个特殊情况,因为它不会显示所有的表单域,它显示两个密码域等等。 我们可以在视图中特别指出以解决这种情况:
def add_stage(request, app_label, model_name):
if app_label == 'auth' and model_name == 'user':
# do special-case code
else:
# do normal code
不过,就如我们多次在这章提到的,这样做并不优雅:因为它把URL逻辑放在了视图中。更优雅的解决方法是,我们要利用URLconf从顶向下的解析顺序这个特点:
urlpatterns = patterns('',
# ...
('^auth/user/add/$', views.user_add_stage),
('^([^/]+)/([^/]+)/add/$', views.add_stage),
# ...
)
在这种情况下,象/auth/user/add/
的请求将会被user_add_stage
视图处理。尽管URL也匹配第二种模式,它会先匹配上面的模式。(这是短路逻辑。)
从URL中捕获文本
每个被捕获的参数将被作为纯Python字符串来发送,而不管正则表达式中的格式。举个例子,在这行URLConf中:
(r'^articles/(?P<year>\d{4})/$', views.year_archive),
尽管\d{4}
将只匹配整数的字符串,但是参数year
是作为字符串传至 views.year_archive()
的,而不是整型。
当你在写视图代码时记住这点很重要,许多Python内建的方法对于接受的对象的类型很讲究。许多内置Python函数是挑剔的(这是理所当然的)只接受特定类型的对象。一个典型的的错误就是用字符串值而不是整数值来创建datetime.date
对象:
>>> import datetime
>>> datetime.date('1993', '7', '9')
Traceback (most recent call last):
...
TypeError: an integer is required
>>> datetime.date(1993, 7, 9)
datetime.date(1993, 7, 9)
回到URLconf和视图处,错误看起来很可能是这样:
# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^articles/(\d{4})/(\d{2})/(\d{2})/$', views.day_archive),
)
# views.py
import datetime
def day_archive(request, year, month, day):
# The following statement raises a TypeError!
date = datetime.date(year, month, day)
因此,day_archive()
应该这样写才是正确的:
def day_archive(request, year, month, day):
date = datetime.date(int(year), int(month), int(day))
注意,当你传递了一个并不完全包含数字的字符串时,int()
会抛出ValueError
的异常,不过我们已经避免了这个错误,因为在URLconf的正则表达式中已经确保只有包含数字的字符串才会传到这个视图函数中。
决定URLconf搜索的东西
当一个请求进来时,Django试着将请求的URL作为一个普通Python字符串进行URLconf模式匹配(而不是作为一个Unicode字符串)。这并不包括 GET
或 POST
参数或域名。它也不包括第一个斜杠,因为每个URL必定有一个斜杠。
例如,在向 http://www.example.com/myapp/的请求中,Django将试着去匹配myapp/
。在向http://www.example.com/myapp/?page=3的请求中,Django同样会去匹配 myapp/
。
在解析URLconf时,请求方法(例如,POST
,GET
,HEAD
)并不会被考虑。换而言之,对于相同的URL的所有请求方法将被导向到相同的函数中。因此根据请求方法来处理分支是视图函数的责任。
视图函数的高级概念
说到关于请求方法的分支,让我们来看一下可以用什么好的方法来实现它。考虑这个URLconf/view
设计:
# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
# ...
(r'^somepage/$', views.some_page),
# ...
)
# views.py
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render_to_response
def some_page(request):
if request.method == 'POST':
do_something_for_post()
return HttpResponseRedirect('/someurl/')
elif request.method == 'GET':
do_something_for_get()
return render_to_response('page.html')
else:
raise Http404()
在这个示例中, some_page()
视图函数对 POST
和 GET
这两种请求方法的处理完全不同。它们唯一的共同点是共享一个URL地址: /somepage/.
正如大家所看到的,在同一个视图函数中对 POST
和 GET
进行处理是一种很初级也很粗糙的做法。一个比较好的设计习惯应该是,用两个分开的视图函数——一个处理 POST
请求,另一个处理 GET
请求,然后在相应的地方分别进行调用。
我们可以像这样做:先写一个视图函数然后由它来具体分派其它的视图,在之前或之后可以执行一些我们自定的程序逻辑。下边的示例展示了这个技术是如何帮我们改进前边那个简单的 some_page()
视图的:
# views.py
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render_to_response
def method_splitter(request, GET=None, POST=None):
if request.method == 'GET' and GET is not None:
return GET(request)
elif request.method == 'POST' and POST is not None:
return POST(request)
raise Http404
def some_page_get(request):
assert request.method == 'GET'
do_something_for_get()
return render_to_response('page.html')
def some_page_post(request):
assert request.method == 'POST'
do_something_for_post()
return HttpResponseRedirect('/someurl/')
# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
# ...
(r'^somepage/$', views.method_splitter, {'GET': views.some_page_get, 'POST': views.some_page_post}),
# ...
)
让我们从头看一下代码是如何工作的:
我们写了一个新的视图,
method_splitter()
,它根据request.method
返回的值来调用相应的视图。可以看到它带有两个关键参数,GET
和POST
,也许应该是视图函数 。如果request.method
返回GET
,那它就会自动调用GET
视图。 如果request.method
返回的是POST
,那它调用的就是POST
视图。 如果request.method
返回的是其它值(如:HEAD
),或者是没有把GET
或POST
提交给此函数,那它就会抛出一个Http404
错误。在URLconf中,我们把
/somepage/
指到method_splitter()
函数,并把视图函数额外需要用到的GET
和POST
参数传递给它。最终,我们把
some_page()
视图分解到两个视图函数中some_page_get()
和some_page_post()
。这比把所有逻辑都挤到一个单一视图的做法要优雅得多。注意,在技术上这些视图函数就不用再去检查
request.method
了,因为method_splitter()
已经替它们做了。(比如,some_page_post()
被调用的时候,我们可以确信request.method
返回的值是post
。)当然,这样做不止更安全也能更好的将代码文档化,这里我们做了一个假定,就是request.method
能象我们所期望的那样工作。
现在我们就拥有了一个不错的,可以通用的视图函数了,里边封装着由 request.method
的返回值来分派不同的视图的程序。关于method_splitter()
就不说什么了,当然,我们可以把它们重用到其它项目中。
然而,当我们做到这一步时,我们仍然可以改进method_splitter
。从代码我们可以看到,它假设 Get
和 POST
视图除了 request
之外不需要任何其他的参数。那么,假如我们想要使用 method_splitter
与那种会从URL里捕捉字符,或者会接收一些可选参数的视图一起工作时该怎么办呢?
为了实现这个,我们可以使用Python中一个优雅的特性带*
的可变参数 我们先展示这些例子,接着再进行解释
def method_splitter(request, *args, **kwargs):
get_view = kwargs.pop('GET', None)
post_view = kwargs.pop('POST', None)
if request.method == 'GET' and get_view is not None:
return get_view(request, *args, **kwargs)
elif request.method == 'POST' and post_view is not None:
return post_view(request, *args, **kwargs)
raise Http404
这里,我们重构method_splitter()
,去掉了GET
和POST
两个关键字参数,改而支持使用*args
和和**kwargs
(注意*
号)这是一个Python特性,允许函数接受动态的、可变数量的、参数名只在运行时可知的参数。如果你在函数定义时,只在参数前面加一个*
号,所有传递给函数的参数将会保存为一个元组。如果你在函数定义时,在参数前面加两个*号,所有传递给函数的关键字参数,将会保存为一个字典。
例如,对于这个函数
def foo(*args, **kwargs):
print "Positional arguments are:"
print args
print "Keyword arguments are:"
print kwargs
看一下它是怎么工作的
>>> foo(1, 2, 3)
Positional arguments are:
(1, 2, 3)
Keyword arguments are:
{}
>>> foo(1, 2, name='Adrian', framework='Django')
Positional arguments are:
(1, 2)
Keyword arguments are:
{'framework': 'Django', 'name': 'Adrian'}
回过头来看,你能发现我们用method_splitter()
和*args
接受**kwargs
函数参数并把它们传递到正确的视图。any
但是在我们这样做之前,我们要调用两次获得参数kwargs.pop()GETPOST
,如果它们合法的话。(我们通过指定pop的缺省值为None
,来避免由于一个或者多个关键字缺失带来的KeyError
)
包装视图函数
我们最终的视图技巧利用了一个高级python技术。假设你发现自己在各个不同视图里重复了大量代码,就像 这个例子:
def my_view1(request):
if not request.user.is_authenticated():
return HttpResponseRedirect('/accounts/login/')
# ...
return render_to_response('template1.html')
def my_view2(request):
if not request.user.is_authenticated():
return HttpResponseRedirect('/accounts/login/')
# ...
return render_to_response('template2.html')
def my_view3(request):
if not request.user.is_authenticated():
return HttpResponseRedirect('/accounts/login/')
# ...
return render_to_response('template3.html')
这里,每一个视图开始都检查request.user
是否是已经认证的,是的话,当前用户已经成功登陆站点否则就重定向/accounts/login/
(注意,虽然我们还没有讲到request.user
,但是14章将要讲到它.就如你所想像的,request.user
描述当前用户是登陆的还是匿名)。
如果我们能够丛每个视图里移除那些重复代,并且只在需要认证的时候指明它们,那就完美了。我们能够通过使用一个视图包装达到目的。花点时间来看看这个:
def requires_login(view):
def new_view(request, *args, **kwargs):
if not request.user.is_authenticated():
return HttpResponseRedirect('/accounts/login/')
return view(request, *args, **kwargs)
return new_view
函数requires_login
,传入一个视图函数view
,然后返回一个新的视图函数new_view
。这个新的视图函数new_view
在函数requires_login
内定义处理request.user.is_authenticated()
这个验证,从而决定是否执行原来的view
函数。
现在,我们可以从views
中去掉if not request.user.is_authenticated()
验证。我们可以在URLconf中很容易的用requires_login
来包装实现.
from django.conf.urls.defaults import *
from mysite.views import requires_login, my_view1, my_view2, my_view3
urlpatterns = patterns('',
(r'^view1/$', requires_login(my_view1)),
(r'^view2/$', requires_login(my_view2)),
(r'^view3/$', requires_login(my_view3)),
)
优化后的代码和前面的功能一样,但是减少了代码冗余现在我们建立了一个漂亮,通用的函数requires_login()
来帮助我们修饰所有需要它来验证的视图
{$ activeFileHint $}